/*
 * Fighter.java
 *
 * Created on 29 October 2006, 21:23
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package digitrix.fightclub.game;

import java.util.Enumeration;
import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.*;
import java.util.Vector;

/**
 *
 * @author Josh
 */
public abstract class Actor extends Sprite {
    
    private static final int HITS_BEFORE_FALLING = 3;
    
    // generic states that every actor has associated to it
    public static final int STATE_STANDING      = 0;
    public static final int STATE_WALKING       = 1;
    public static final int STATE_DYING         = 3;
    public static final int STATE_FALLING       = 4;
    public static final int STATE_ATTACK1       = 5;
    public static final int STATE_ATTACK2       = 6;
    public static final int STATE_ATTACK3       = 7;
    
    protected String _name = ""; 
    protected int _maxHealth;
    protected int _health;
    protected int _lives;
    protected int _velocityX;
    protected int _velocityY;
    protected int _speedX;
    protected int _speedY;
    protected int _startingX;
    protected int _startingY;
    protected boolean _facingLeft = false;
    protected int _blinkTimmer = -1;
    protected int _hitCount = 0; // every time the actor is hit (below the damage
    
    protected Vector _states;
    protected ActorState _currentState;
    
    private int _orgHeight = 0; // make sure the user does not move off the group
    private int _oldX = 0;
    private int _oldY = 0;
    private boolean _movedLastTick = false;
    
    /** Creates a new instance of Fighter */
    public Actor( Image image, int width, int height ) {
        super( image, width, height );
        defineReferencePixel( this.getWidth()/2, this.getHeight()/2 );
        setVisible( false );
        
        _orgHeight = height;
        _states = new Vector();
    }
    
    public void resetReferencePoint(){
        defineReferencePixel( this.getWidth()/2, this.getHeight()/2 );
    }
    
    /**
     * Initilise the actor; usually called when creating a new actor or reusing an
     * existing actor
     * @param maxHealth assigns the maximum hit points this actor has
     * @param lives defines how many chances this actor has in life
     * @param hSpeed assigns the horizontal speed of the actor (jumping capability of the actor)
     * @param vSpeed assigns the vertical speed of the actor (the initial vertical velocity of a jump)
     * @param posX assigns the starting x position
     * @param posY assigns the starting y position
     **/
    public void initActor( int maxHealth, int lives, int xSpeed, int vSpeed, int posX, int posY, boolean facingLeft ) throws Exception {
        setMaxHealth(maxHealth);
        setLives(lives);
        setSpeedX(xSpeed);
        setSpeedY(vSpeed);
        setStartingX(posX);
        setStartingY(posY);
        setFacingLeft(facingLeft);
        
        // ensure we have a standing state; otherwise throw an exception
        if( _states.size() <= 0 )
            throw new Exception( "Actor States have not been initiated" );
        
        reset();
    }
    
    /**
     * Reset the Actor back to its initial state
     **/
    public void reset() {
        setHealth(getMaxHealth());
        setVelocityX(0);
        setVelocityY(0);
        
        setState( STATE_STANDING );
        
        setInitialPosition( getStartingX(), getStartingY());
        setVisible( true );
    }
    
    /**
     * Only right direction based sprites are stored in the image, therefore to make
     * our actor face left we must transform the image
     **/
    public void transform(){
        setTransform( this.TRANS_MIRROR );
    }
    
    /**
     * Called when there is a conflict between two sprites or dangerous tile
     * @param damage is the value of the attackers strength; this value will be deducted from the actors health
     **/
    public void takeDamage( int damage ){
        if( damage <= 0 )
            return;
        
        int stateID = _currentState.getStateID();
        
        if( stateID == STATE_DYING || stateID == STATE_FALLING )
            return;
        
        // if the player is in a attack and the damage is 0 (meaning that the player is in a state were they are preparing to
        // attack) then set the damage to max( damange, selfs attack damage) and change the state to falling (eg if a player
        // is attempting a power move and gets hit by a punch then the punish the user trying the power move).
        if( (stateID == STATE_ATTACK1 || stateID == STATE_ATTACK2 || stateID == STATE_ATTACK3) && _currentState.getDamage() <= 0 ){
            if( _currentState.getDamage() > damage )
                damage = _currentState.getDamage();
            
            _hitCount = 0; // reset hit count
            setHealth(getHealth() - damage);
            setState( STATE_FALLING );
        }
        // else if hit by attack 1 or attack 2 (punch/kick) then throw the character back a little and
        // blink the character to notify the user that their player has been hit
        else if( damage < 5 || !hasState(STATE_FALLING)){
            setHealth(getHealth() - damage);
            initBlinking( 1000 );
            
            if( _hitCount >= HITS_BEFORE_FALLING && hasState(STATE_FALLING) ){
                _hitCount = 0; // reset hit count
                setState( STATE_FALLING );
            } else{                
                if( _facingLeft )
                    move( 2, 0 );
                else
                    move( -2, 0 );
                
                _hitCount++; 
            }
        } else{
            _hitCount = 0; // reset hit count
            setHealth(getHealth() - damage);
            setState( STATE_FALLING );
        }
        
        System.out.println( "[INFO] Actor.takeDamage(1); damage = " + damage + " for player " + (!isFacingLeft() ? "LEFT" : "RIGHT") );
        
        if( getHealth() <= 0 )
            setState( STATE_DYING );
    }
    
    /*
     * TODO: method used to initilise blinking
     */
    private void initBlinking( int timeToBlink ){
        
    }
    
    /**
     * Called before update() if the actor collided with a
     * tile horizontally.
     */
    public void collideHorizontal() {
        setVelocityX( 0 );
    }
    
    /*
     * Only move the fighter when their state is either standing or walking (ie cannot work when
     * attacking, falling, or dying).
     */
    public void moveLeft(){
        int stateID = getState().getStateID();
        if(  stateID == STATE_STANDING || stateID == STATE_WALKING )
            setVelocityX( -_speedX );
    }
    
    public void moveRight(){
        int stateID = getState().getStateID();
        if(  stateID == STATE_STANDING || stateID == STATE_WALKING )
            setVelocityX( _speedX );
    }
    
    public int getFightingDamage(){
        return _currentState.getDamage();
    }
    
    /* Search through all of the actor states looking for a key state that matches the one
     * passed via a parameter; if found then return the actor state otherwise return null */
    public ActorState getFightingStateByInput( int keyState ){
        
        // ensure that fire was pressed (ie all moves are a combination of fire (by itself) or combined with
        // a direction
        if( (keyState & GameCanvas.FIRE_PRESSED) == 0 )
            return null;
        
        Enumeration enumStates = _states.elements();
        while( enumStates.hasMoreElements() ){
            ActorState state = (ActorState)enumStates.nextElement();
            
            // if state is either standing, walking, falling or dying then skip (obviously they're not fighting states)
            int stateID = state.getStateID();
            if( stateID == STATE_STANDING || stateID == STATE_WALKING || stateID == STATE_FALLING || stateID == STATE_DYING )
                continue; // skip to the next state
            
            if( (keyState & GameCanvas.LEFT_PRESSED) != 0 && state.getKeySequence() == ActorState.KEY_FIRE_LEFT )
                return state;
            else if( (keyState & GameCanvas.RIGHT_PRESSED) != 0 && state.getKeySequence() == ActorState.KEY_FIRE_RIGHT )
                return state;
            else if( (keyState & GameCanvas.DOWN_PRESSED) != 0 && state.getKeySequence() == ActorState.KEY_FIRE_DOWN )
                return state;
            else if( (keyState & GameCanvas.UP_PRESSED) != 0 && state.getKeySequence() == ActorState.KEY_FIRE_UP )
                return state;
            else if( state.getKeySequence() == ActorState.KEY_FIRE_ONLY && ( (keyState & GameCanvas.UP_PRESSED) == 0 && (keyState & GameCanvas.DOWN_PRESSED) == 0
                    && (keyState & GameCanvas.RIGHT_PRESSED) == 0 && (keyState & GameCanvas.LEFT_PRESSED) == 0) )
                return state;
        }
        
        return null;
    }
    
    //<editor-fold defaultstate="collapsed" desc="Mutators">
    
    public String getName(){
        return _name; 
    }
    
    public void setName( String name ){
        _name = name; 
    }
    
    public int getMaxHealth() {
        return _maxHealth;
    }
    
    public void setMaxHealth(int maxHealth) {
        this._maxHealth = maxHealth;
    }
    
    public int getHealth() {
        return _health;
    }
    
    public void setHealth(int health) {
        this._health = health;
    }
    
    public int getLives() {
        return _lives;
    }
    
    public void setLives(int lives) {
        this._lives = lives;
    }
    
    public int getVelocityX() {
        return _velocityX;
    }
    
    public void setVelocityX(int velocityX) {
        this._velocityX = velocityX;
    }
    
    public int getVelocityY() {
        return _velocityY;
    }
    
    public void setVelocityY(int velocityY) {
        this._velocityY = velocityY;
    }
    
    public int getSpeedX() {
        return _speedX;
    }
    
    public void setSpeedX(int speedX) {
        this._speedX = speedX;
    }
    
    public int getSpeedY() {
        return _speedY;
    }
    
    public void setSpeedY(int speedY) {
        this._speedY = speedY;
    }
    
    public int getStartingX() {
        return _startingX;
    }
    
    public void setStartingX(int startingX) {
        this._startingX = startingX;
    }
    
    public int getStartingY() {
        return _startingY;
    }
    
    public void setStartingY(int startingY) {
        this._startingY = startingY;
    }
    
    public int getOrginalHeight(){
        return _orgHeight;
    }
    
    public boolean isFacingLeft() {
        return _facingLeft;
    }
    
    public void setFacingLeft(boolean facingLeft) {
        this._facingLeft = facingLeft;
        
        // transform if facing right
        if( !_facingLeft )
            transform();
    }
    
    public ActorState getCurrentState() {
        return _currentState;
    }
    
    public void setCurrentState(ActorState currentState) {
        this._currentState = currentState;
    }
    
    /*public int getStateByID(){
        if( getCurrentState() == null )
            return -1;
     
        //for( int i=0; i<_states.size(); i++ ){
        //    if( getCurrentState() == (ActorState)_states.elementAt( i ) )
        //        return i;
        //}
     
        return -1; // could not find state
    }*/
    
    private boolean hasState( int stateID ){
        if( _states == null )
            return false;
        
        Enumeration enumStates = _states.elements();
        while( enumStates.hasMoreElements() ){
            if( ((ActorState)enumStates.nextElement()).getStateID() == stateID )
                return true;
        }
        
        return false;
    }
    
    public void setState( int stateID ){
        //if( _states == null || state >= _states.size() )
        //    return;
        //
        //setState( (ActorState)_states.elementAt( state ) );
        if( _states == null )
            return;
        
        Enumeration enumStates = _states.elements();
        while( enumStates.hasMoreElements() ){
            ActorState state = (ActorState)enumStates.nextElement();
            if( state.getStateID() == stateID )
                setState( state );
        }
        
    }
    
    public void setState( ActorState newState ){
        //TODO: implement method details in child class
        if( _currentState == newState )
            return; 
        
        newState.activateState();
    }
    
    public ActorState getState(){
        return getCurrentState();
    }
    
    public void addState( ActorState state, int index ){
        _states.insertElementAt( state, index );
    }
    
    public void addState( ActorState state ){
        _states.addElement( state );
    }
    
    public void addStates( Vector states ){
        _states = states;
    }
    
    public boolean movedLastTick(){
        return _movedLastTick;
    }
    
    //</editor-fold>
    
    //<editor-fold defaultstate="collapsed" desc="Helper methods for moving">
    
    /* method called with using velocity (eg state movement) */
    public void forcedMove( int x, int y ){
        int newX = getX() + x;
        
        if( newX > World.getInstance().getWorldLeftEdge() && newX < (World.getInstance().getWorldRightEdge()-getWidth()) )
            move( x, y );
    }
    
    public void move( int x, int y ){
        _oldX = getX();
        _oldY = getY();
        
        super.move( x, y );
    }
    
    public void setPosition( int x, int y ){
        _oldX = getX();
        _oldY = getY();
        
        super.setPosition( x, y );
    }
    
    public void reserveMove(){
        super.setPosition( _oldX, _oldY );
    }
    
    public void setInitialPosition( int x, int y ){
        _oldX = getStartingX();
        _oldY = getStartingY();
        
        super.setPosition( x, y );
    }
    
    //</editor-fold>        
    
    public boolean inFightingState(){
        return _currentState.getStateID() > 4;
    }
    
    public void tick( long elapsedTime ){
        getCurrentState().tick( elapsedTime );
        
        _movedLastTick = false;
        
        // Move character if not dead
        if( getCurrentState().getStateID() != STATE_DYING && getCurrentState().getStateID() != STATE_FALLING ){
            if( getVelocityX() == 0 && getCurrentState().getStateID() == STATE_WALKING ){
                setState( STATE_STANDING );
            } else if( getVelocityX() != 0 ){
                _movedLastTick = true; // take note of moving for this tick (to help the game manager work out collisoins etc)
                
                // animate walking
                if( getCurrentState().getStateID() == STATE_STANDING )
                    setState( STATE_WALKING );
                
                // reached the ends of the earth?
                if( getVelocityX() > 0 ){
                    if( (getX() + getVelocityX()) > (World.getInstance().getWorldRightEdge()-getWidth()) )
                        collideHorizontal();
                } else{
                    if( (getX() - getVelocityX()) < World.getInstance().getWorldLeftEdge() )
                        collideHorizontal();
                }
                
                // move character
                this.move( getVelocityX(), 0 );
                
                // reset velocity
                setVelocityX( 0 );
            }
        }
        
        //TODO: implement method details in child class
    }
    
    public abstract void handleInput( int keyState );
    public abstract void stateComplete( ActorState state );
}
